package com.lantosec.hmrp.modules.workflow.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lantosec.hmrp.core.kit.Result;
import com.lantosec.hmrp.core.util.FieldUtils;
import com.lantosec.hmrp.modules.sysdata.entity.SysModelForm;
import com.lantosec.hmrp.modules.sysdata.service.ISysDataProcessService;
import com.lantosec.hmrp.modules.sysdata.service.ISysModelFormService;
import com.lantosec.hmrp.modules.sysdata.service.ISysProcessFormService;
import com.lantosec.hmrp.modules.sysdata.service.ISysProcessNotifyService;
import com.lantosec.hmrp.modules.workflow.mapper.RepositoryMapper;
import com.lantosec.hmrp.modules.workflow.vo.*;
import com.lantosec.hmrp.core.kit.PageVO;
import com.lantosec.hmrp.core.util.DateUtils;
import com.lantosec.hmrp.core.util.FlowableUtils;
import com.lantosec.hmrp.modules.workflow.service.IWorkflowRepositoryService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Primary
@Log4j2
public class WorkflowRepositoryServiceImpl implements IWorkflowRepositoryService {
    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RepositoryMapper actRepositoryMapper;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ISysModelFormService sysModelFormService;

    @Autowired
    private ISysProcessFormService sysProcessFormService;

    @Autowired
    private ISysDataProcessService sysDataProcessService;

    /**
     * 分页查询模型数据
     *
     * @param page        分页对象
     * @param modelName   模型名称
     * @param modelTypeId 模型类型
     * @return 模型数据列表的分页对象
     */
    @Override
    public PageVO<FlowModelVO> modelList(PageVO page, String modelName, String modelTypeId) {
        modelName = StringUtils.isNotBlank(modelName) ? modelName : null;
        modelTypeId = StringUtils.isNotBlank(modelTypeId) ? modelTypeId : null;
        return actRepositoryMapper.queryModelList(page, modelName, modelTypeId);
    }

    @Override
    public ModelVO queryModelById(String modelId) {
        try {
            Model model = repositoryService.getModel(modelId);
            ModelVO modelVO = new ModelVO();
            modelVO.setModelId(model.getId());
            modelVO.setModelName(model.getName());

            JSONObject jsonObject = JSON.parseObject(model.getMetaInfo());
            modelVO.setDescription(String.valueOf(jsonObject.get("description")));

            SysModelForm modelForm = sysModelFormService.selectByModelId(modelId);
            if(!Objects.isNull(modelForm)) {
                modelVO.setFormTableId(modelForm.getFormTableId());
                modelVO.setProInsNameRule(modelForm.getProInsNameRule());
                modelVO.setProcessTypeId(modelForm.getProcessType());
            }

            String xml = modelEditorSource(modelId);
            // xml文件返回给前端编辑前替换自定义属性的名称，防止前端js解析失败
            xml = com.lantosec.hmrp.core.util.StringUtils.removeCustomAttrMark(xml);
            modelVO.setXml(xml);
            modelVO.setSvg(modelEditorSourceExtra(modelId));

            return modelVO;
        } catch (Exception ex) {
            log.error("查询Flowable模型错误.", ex);
            throw new FlowableException("查询Flowable模型错误.", ex);
        }
    }

    /**
     * 模型的编辑源码，XML文件内容
     *
     * @param modelId act_re_model表ID
     * @return XML文件内容
     */
    @Override
    public String modelEditorSource(String modelId) {
        if (StringUtils.isBlank(modelId)) {
            return null;
        }

        try {
            byte[] modelEditorSource = repositoryService.getModelEditorSource(modelId);
            String source = new String(modelEditorSource, "UTF-8");
            return source;
        }
        catch (Exception ex){
        }
        return null;
    }

    /**
     * 模型的编辑图片源码，SVG文件内容转成Base64
     *
     * @param modelId act_re_model表ID
     * @return SVG文件Base64内容
     */
    @Override
    public String modelEditorSourceExtra(String modelId) {
        if (StringUtils.isBlank(modelId)) {
            return null;
        }
        try {
            byte[] modelEditorSource = repositoryService.getModelEditorSourceExtra(modelId);
            String source = new String(modelEditorSource, "UTF-8");
            if(StringUtils.isNotBlank(source)){
                return FlowableUtils.svg2Base64(source);
            }
        }
        catch (Exception ex){
        }
        return null;
        //return FlowableUtils.svg2Base64(actRepositoryMapper.queryEditorSource(bytearrayId));
    }

    /**
     * 分页查询部署的流程
     *
     * @param name      流程名称
     * @param startTime 部署开始时间
     * @param endTime   部署开始
     * @return 流程列表的分页对象
     */
    @Override
    public PageVO deploymentList(PageVO page, String name, String startTime, String endTime) {
        name = StringUtils.isNotBlank(name) ? name : null;
        startTime = StringUtils.isNotBlank(startTime) ? startTime : null;
        if (StringUtils.isNotBlank(endTime)) {
            endTime = DateUtils.dateToStr(DateUtils.addDay(DateUtils.strToDate(endTime, DateUtils.SHORTFORMAT), 1), DateUtils.SHORTFORMAT);
        } else {
            endTime = null;
        }
        //List<Deployment> list = repositoryService.createDeploymentQuery().listPage(page.getPageNo().intValue(), page.getPageSize().intValue());;
        return actRepositoryMapper.queryDeploymentList(page, name, startTime, endTime);
    }

    @Override
    public List<FlowProcdefVO> processDefinitionList() {
        return actRepositoryMapper.queryProcessList();
    }

    /**
     * 添加流程模型并返回modelId
     *
     * @param modelName      模型名称
     * @param category       模型分类
     * @return 模型ID
     */
    @Override
    public Result<String> createModel(String modelName, String category) {
        if (StringUtils.isBlank(modelName) || StringUtils.isBlank(category)) {
            return Result.fail("缺少参数");
        }
        try {
            long count = repositoryService.createModelQuery().modelName(modelName).count();
            if (count > 0) {
                return Result.fail("已经存在相同的模型名称");
            }
            ObjectMapper objectMapper = new ObjectMapper();
            /*ObjectNode editorNode = objectMapper.createObjectNode();
            editorNode.put("id", "");
            editorNode.put("resourceId", "");
            ObjectNode stencilSetNode = objectMapper.createObjectNode();
            stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");  //命名空间(禁止修改)
            stencilSetNode.put("author", "ycy");                                     //流程节点作者
            editorNode.set("stencilset", stencilSetNode);*/

            ObjectNode modelObjectNode = objectMapper.createObjectNode();
            modelObjectNode.put("name", modelName);                                     //模型名称
            modelObjectNode.put("revision", 1);                                      //模型版本
            modelObjectNode.put("description", "");                                  //模型描述
            Model modelData = repositoryService.newModel();
            modelData.setCategory(category);                                                        //模型分类
            modelData.setDeploymentId(null);
            modelData.setKey(null);
            modelData.setMetaInfo(modelObjectNode.toString());
            modelData.setName(modelName);                                                           //模型名称
            modelData.setTenantId("");
            modelData.setVersion(1);
            repositoryService.saveModel(modelData);                    //保存模型,存储数据到表：act_re_model 流程设计模型部署表
            //repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes(StandardCharsets.UTF_8));//保存资源,存储数据到表：act_ge_bytearray 二进制数据表
            //保存资源,存储数据到表：act_ge_bytearray 二进制数据表
            String editorSource = "";
            repositoryService.addModelEditorSource(modelData.getId(), editorSource.getBytes(StandardCharsets.UTF_8));
            return Result.ok(modelData.getId());
        } catch (Exception e) {
            log.error("创建Flowable模型错误.", e);
            return Result.fail("创建Flowable模型错误");
        }
    }

    /**
     * 保存流程模型数据
     *
     */
    @Override
    public void saveModel(ModelVO saveModel) {
        try {
            if(Objects.isNull(saveModel) || StringUtils.isBlank(saveModel.getModelId())) {
                throw new FlowableException("缺少必要的参数");
            }
            Model model = repositoryService.getModel(saveModel.getModelId());
            ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
            modelJson.put("name", saveModel.getModelName());
            modelJson.put("description", saveModel.getDescription());
            model.setMetaInfo(modelJson.toString());
            model.setName(saveModel.getModelName());
            repositoryService.saveModel(model);
            String xmlStr = com.lantosec.hmrp.core.util.StringUtils.addCustomAttrMark(saveModel.getXml());
            repositoryService.addModelEditorSource(model.getId(), xmlStr.getBytes(StandardCharsets.UTF_8));
            if (StringUtils.isNotBlank(saveModel.getSvg())) {
                repositoryService.addModelEditorSourceExtra(model.getId(), FlowableUtils.svg2Png(saveModel.getSvg()));
            }
            // 保存表单Id与工作流模型的关系
            sysModelFormService.save(model.getId(), saveModel.getFormTableId(), saveModel.getStartLayoutId(),
                    saveModel.getStartTaskId(), model.getCategory(), saveModel.getModelName(), saveModel.getProInsNameRule());

        } catch (Exception e) {
            log.error("保存Flowable模型错误.", e);
            throw new FlowableException("保存Flowable模型错误.", e);
        }
    }

    /**
     * 删除流程模型，慎用之
     *
     * @param modelId 模型ID
     */
    @Override
    public void deleteModel(String modelId) {
        try {
            Arrays.asList(modelId.split(",")).forEach(id -> {
                repositoryService.deleteModel(id);
                sysModelFormService.deleteByModelId(id);
            });
        } catch (Exception e) {
            log.error("删除流程模型错误.", e);
            throw new FlowableException("删除流程模型错误.", e);
        }
    }

    /**
     * 删除部署的流程
     *
     * @param deploymentId 部署ID
     */
    @Override
    public void deleteDeployment(String deploymentId) {
        try {
            //不带级联的删除，此删除只能删除没有启动的流程，否则抛出异常 .act_re_deployment，act_re_procdef 和act_ge_bytearray 三张表中相关数据都删除
            //repositoryService.deleteDeployment(deploymentId);
            //级联删除，不管流程是否启动，都可以删除
            repositoryService.deleteDeployment(deploymentId, true);
            sysProcessFormService.deleteByDeploymentId(deploymentId);
            sysDataProcessService.deleteByDeploymentId(deploymentId);
        } catch (Exception e) {
            log.error("删除部署的流程错误.", e);
            throw new FlowableException("删除部署的流程错误，已经启动的部署流程不能删除。", e);
        }
    }

    /**
     * 激活流程定义
     *
     * @param deploymentId 流程定义ID
     */
    @Override
    public void activateProcessDefinitionById(String deploymentId) {
        try {
            repositoryService.activateProcessDefinitionById(deploymentId, true, null);
        } catch (Exception e) {
            log.error("激活流程定义错误.", e);
            throw new FlowableException("激活流程定义错误.", e);
        }

    }

    /**
     * 挂起流程定义
     * 如果流程定义处于挂起状态，则不可能基于流程定义启动新的流程实例。
     *
     * @param deploymentId 流程定义ID
     */
    @Override
    public void suspendProcessDefinitionById(String deploymentId) {
        try {
            boolean suspendProcessInstances = true; //如果为真，所提供的流程定义的所有流程实例也将被挂起。
            Date suspensionDate = null; //进程定义被挂起的日期。如果为空，进程定义将立即挂起。注意:作业执行程序需要激活才能使用它!
            repositoryService.suspendProcessDefinitionById(deploymentId, suspendProcessInstances, suspensionDate);
        } catch (Exception e) {
            log.error("挂起流程定义错误.", e);
            throw new FlowableException("挂起流程定义错误.", e);
        }
    }

    /**
     * 根据流程部署ID查询流程定义信息，查询当前版本最高的一条
     *
     * @param deploymentId 流程定义ID
     */
    @Override
    public FlowProcdefVO procdefByDeploymentId(String deploymentId) {
        return actRepositoryMapper.queryProcde(deploymentId);
    }

    /**
     * 根据流程Key获取最新的流程定义
     *
     * @param definitionKey 流程KEY
     */
    @Override
    public FlowProcdefVO getProcessDefinitionByKey(String definitionKey) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(definitionKey).latestVersion().singleResult();
        if (Objects.isNull(processDefinition)) {
            return null;
        }
        FlowProcdefVO flowProcdefVO = new FlowProcdefVO(processDefinition);
        String resourceName = flowProcdefVO.getResourceName();
        if (StringUtils.isEmpty(flowProcdefVO.getName()) && StringUtils.isNotEmpty(resourceName) && resourceName.contains(".")) {
            flowProcdefVO.setName(resourceName.substring(0, resourceName.indexOf(".")));
        }
        return flowProcdefVO;
    }

    /**
     * 获取节点对象
     * @param userTaskId          节点id
     * @param processDefinitionId 流程定义id
     * @return
     */
    @Override
    public UserTask getUserTask(String userTaskId, String processDefinitionId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        return getUserTask(userTaskId, bpmnModel);
    }

    /**
     * 获取节点对象
     * @param userTaskId    节点id
     * @param bpmnModel     bpmn对象
     * @return
     */
    private UserTask getUserTask(String userTaskId, BpmnModel bpmnModel) {
        List<Process> processes = bpmnModel.getProcesses();
        if (CollectionUtils.isNotEmpty(processes)) {
            for(Process process : processes) {
                UserTask userTask = recursiveUserTask(userTaskId, process.getFlowElements());
                if(!Objects.isNull(userTask)){
                    return userTask;
                }
            }
        }
        return null;
    }

    /**
     * 递归查询
     * @return
     */
    private UserTask recursiveUserTask(String userTaskId, Collection<FlowElement> flowElements) {
        for (FlowElement item : flowElements) {
            if (item instanceof UserTask) {
                if (item.getId().equals(userTaskId)) {
                    return (UserTask) item;
                }
            } else if (item instanceof SubProcess) {
                // 如果是嵌入子流程，那么从子流程递归找
                UserTask userTask = recursiveUserTask(userTaskId, ((SubProcess) item).getFlowElements());
                if (!Objects.isNull(userTask)) {
                    return userTask;
                }
            }else if (item instanceof CallActivity) {
                // 如果是外部子流程，那么从子流程递归找
                CallActivity callActivity = (CallActivity) item;
                String processKey = callActivity.getCalledElement();
                // 查询流程定义
                ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processKey).latestVersion().singleResult();
                if(!Objects.isNull(processDefinition)){
                    UserTask userTask = getUserTask(userTaskId, processDefinition.getId());
                    if (!Objects.isNull(userTask)) {
                        return userTask;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 获取节点对象的自定义属性列表
     *
     * @param userTaskId          节点id
     * @param processDefinitionId 流程定义id
     * @param customAttributeName  属性名
     * @return
     */
    @Override
    public List<ExtensionAttribute> getCustomAttribute(String userTaskId, String processDefinitionId, String customAttributeName) {
        UserTask userTask = this.getUserTask(userTaskId, processDefinitionId);
        if(!Objects.isNull(userTask)) {
            Map<String, List<ExtensionAttribute>> attributeList = userTask.getAttributes();
            if (MapUtils.isNotEmpty(attributeList)) {
                List<ExtensionAttribute> values = attributeList.get(customAttributeName);
                if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(values)) {
                    return values;
                }
            }
        }
        /*Map<String, List<ExtensionElement>> extensionElements = userTask.getExtensionElements();
        if (MapUtils.isNotEmpty(extensionElements)) {
            List<ExtensionElement> values = extensionElements.get(customPropertyName);
            if (CollectionUtils.isNotEmpty(values)) {
                return values;
            }
        }*/

        return null;
    }

    @Override
    public CustomAttributeVO getCustomAttribute(String userTaskId, String processDefinitionId) {
        UserTask userTask = this.getUserTask(userTaskId, processDefinitionId);
        return getCustomAttribute(userTask);
    }

    @Override
    public CustomAttributeVO getCustomAttribute(String userTaskId, BpmnModel bpmnModel) {
        UserTask userTask = this.getUserTask(userTaskId, bpmnModel);
        return getCustomAttribute(userTask);
    }

    @Override
    public CustomAttributeVO getCustomAttribute(UserTask userTask) {
        return getCustomAttribute((FlowElement) userTask);
    }

    private CustomAttributeVO getCustomAttribute(FlowElement flowElement) {
        CustomAttributeVO customAttribute = new CustomAttributeVO();
        if(!Objects.isNull(flowElement)) {
            Map<String, List<ExtensionAttribute>> attributeList = flowElement.getAttributes();
            if (MapUtils.isNotEmpty(attributeList)) {
                Field[] fields = customAttribute.getClass().getDeclaredFields();
                for (int i = 0; i < fields.length; i++) {
                    List<ExtensionAttribute> values = attributeList.get(fields[i].getName());
                    if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(values)) {
                        Object value = values.get(0).getValue();
                        FieldUtils.setFiledValue(fields[i], customAttribute, value);
                    }
                }
            }
        }
        return customAttribute;
    }

    @Override
    public CustomExtensionElementVO getExtensionElement(String flowElementId, String processDefinitionId) {
        CustomExtensionElementVO extensionElementVO = new CustomExtensionElementVO();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        Map<String, List<ExtensionElement>> elementList = bpmnModel.getFlowElement(flowElementId).getExtensionElements();
        if (MapUtils.isNotEmpty(elementList)) {
            Field[] fields = extensionElementVO.getClass().getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                List<ExtensionElement> values = elementList.get(fields[i].getName());
                if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(values)) {
                    Object value = values.get(0).getElementText();
                    FieldUtils.setFiledValue(fields[i], extensionElementVO, value);
                }
            }
        }
        return extensionElementVO;
    }
}
